#pragma once
#include "Runtime/BattleFirePrefix.h"
#include "Char.h"
#define WITH_CASE_PRESERVING_NAME 1
#define NAME_NO_NUMBER_INTERNAL 0
#define NAME_INTERNAL_TO_EXTERNAL(x) (x - 1)
#define NAME_EXTERNAL_TO_INTERNAL(x) (x + 1)

namespace Alice{
    enum class ENameCase : uint8
    {
        CaseSensitive,
        IgnoreCase,
    };
    /** Enumeration for finding name. */
    enum EFindName
    {
        /** 
        * Find a name; return 0/NAME_None/FName() if it doesn't exist.
        * When UE_FNAME_OUTLINE_NUMBER is set, we search for the exact name including the number suffix.
        * Otherwise we search only for the string part.
        */
        FNAME_Find,

        /** Find a name or add it if it doesn't exist. */
        FNAME_Add
    };
    /** Maximum size of name, including the null terminator. */
    enum {NAME_SIZE	= 1024};
    
    struct NameEntryHeader
    {
        uint16 bIsWide : 1;
        uint16 Len : 15;
    };
    struct NameBuffer
    {
        ANSICHAR AnsiName[NAME_SIZE];
    };
	/** Hashes name into 64 bits that determines shard and slot index.
	 *	
	 *	A small part of the hash is also stored in unused bits of the slot and entry. 
	 *	The former optimizes linear probing by accessing less entry data.
	 *	The latter optimizes linear probing by avoiding copying and deobfuscating entry data.
	 *
	 *	The slot index could be stored in the slot, at least in non shipping / test configs.
	 *	This costs memory by doubling slot size but would essentially never touch entry data
	 *	nor copy and deobfuscate a name needlessy. It also allows growing the hash table
	 *	without rehashing the strings, since the unmasked slot index would be known.
	 */
	struct NameHash {
		uint32 ShardIndex;
		uint32 UnmaskedSlotIndex; // Determines at what slot index to start probing
		uint32 SlotProbeHash; // Helps cull equality checks (decode + strnicmp) when probing slots
		NameEntryHeader EntryProbeHeader; // Helps cull equality checks when probing inspects entries
		NameHash(const ANSICHAR* Str, int32 Len) : NameHash(GenerateHash(Str, Len), Len){}
		NameHash(uint64 Hash, int32 Len);
		
		uint32 GetProbeStart(uint32 SlotMask) const;

	    static uint64 AlgorithmId;
	    static uint32 ShardMask;

	    static uint32 GetShardIndex(uint64 Hash);
	    static uint64 GenerateHash(const ANSICHAR* Str, int32 Len);
		static uint32 GetProbeStart(uint32 UnmaskedSlotIndex, uint32 SlotMask);
		bool operator==(const NameHash& Rhs) const
		{
			return  ShardIndex == Rhs.ShardIndex &&
					UnmaskedSlotIndex == Rhs.UnmaskedSlotIndex &&
					SlotProbeHash == Rhs.SlotProbeHash &&
					(uint16&)EntryProbeHeader == (uint16&)Rhs.EntryProbeHeader;
		}
	};

    struct NameStringView
    {
        NameStringView() : mData(nullptr), mLen(0) {}
        NameStringView(const ANSICHAR* inStr, uint32 inLen) : mAnsiStr(inStr), mLen(inLen) {}
        NameStringView(const void* inData, uint32 inLen) : mData(inData), mLen(inLen) {}
        union
        {
            const void* mData;
            const ANSICHAR* mAnsiStr;
        };
        uint32 mLen;
        bool IsNoneString() const;

        uint32 BytesWithTerminator() const
        {
            return (mLen + 1);
        }

        uint32 BytesWithoutTerminator() const
        {
            return mLen;
        }
    };
    
    class Name;
    /** Opaque id to a deduplicated name */
    struct NameEntryId
    {
        constexpr static bool bHasIntrusiveUnsetOptionalState = true;
        
        // Default initialize to be equal to NAME_None
        NameEntryId() : Value(0) {}
        bool IsNone() const
        {
            return Value == 0;
        }

        /** Slow alphabetical order that is stable / deterministic over process runs, ignores case */
        int32 CompareLexical(NameEntryId Rhs) const;
        bool LexicalLess(NameEntryId Rhs) const { return CompareLexical(Rhs) < 0; }

        /** Slow alphabetical order that is stable / deterministic over process runs, case-sensitive */
        int32 CompareLexicalSensitive(NameEntryId Rhs) const;
        bool LexicalSensitiveLess(NameEntryId Rhs) const { return CompareLexicalSensitive(Rhs) < 0; }

        /** Fast non-alphabetical order that is only stable during this process' lifetime */
        int32 CompareFast(NameEntryId Rhs) const { return Value - Rhs.Value; };
        bool FastLess(NameEntryId Rhs) const { return CompareFast(Rhs) < 0; }

        /** Fast non-alphabetical order that is only stable during this process' lifetime */
        bool operator<(NameEntryId Rhs) const { return Value < Rhs.Value; }

        /** Fast non-alphabetical order that is only stable during this process' lifetime */
        bool operator>(NameEntryId Rhs) const { return Rhs.Value < Value; }
        bool operator==(NameEntryId Rhs) const { return Value == Rhs.Value; }
        bool operator!=(NameEntryId Rhs) const { return Value != Rhs.Value; }
        
        /** Comparison against special type for checking TOptional<FName>:IsSet */
        bool operator==(uint32) const
        {
            return Value == ~0u;
        }

        // Returns true if this NameEntryId is not equivalent to NAME_None
        explicit operator bool() const { return Value != 0; }

        /** Get process specific integer */
        uint32 ToUnstableInt() const { return Value; }

        /** Create from unstable int produced by this process */
        static NameEntryId FromUnstableInt(uint32 UnstableInt)
        {
            NameEntryId Id;
            Id.Value = UnstableInt;
            return Id;
        }

    private:
        uint32 Value;
    public:
        friend uint32 GetTypeHash(NameEntryId Id);
    };
    template<class CharType>
    NameHash HashLowerCase(const CharType* Str, uint32 Len)
    {
        CharType LowerStr[NAME_SIZE];
        for (uint32 I = 0; I < Len; ++I)
        {
            LowerStr[I] = TChar<CharType>::ToLower(Str[I]);
        }
        return NameHash(LowerStr, Len);
    }
    template<ENameCase Sensitivity>
    NameHash HashName(NameStringView Name);

    template<>
    inline NameHash HashName<ENameCase::IgnoreCase>(NameStringView Name)
    {
        return HashLowerCase(Name.mAnsiStr, Name.mLen);
    }
    template<>
    inline NameHash HashName<ENameCase::CaseSensitive>(NameStringView Name)
    {
        return NameHash(Name.mAnsiStr, uint32(Name.mLen));
    }

    template<ENameCase Sensitivity>
    struct NameValue
    {
        explicit NameValue(NameStringView InName)
            : Name(InName)
            , Hash(HashName<Sensitivity>(InName))
        {}

        NameValue(NameStringView InName, NameHash InHash)
            : Name(InName)
            , Hash(InHash)
        {}
        
        NameStringView Name;
        NameHash Hash;
#if WITH_CASE_PRESERVING_NAME
        NameEntryId ComparisonId;
#endif
    };
    struct NameEntryIds
    {
        NameEntryId ComparisonId,DisplayId;
    };
    /**
     * A global deduplicated name stored in the global name table.
     */
    struct NameEntry
    {
    public:
        NameEntryId ComparisonId;
        NameEntryHeader Header;
        char			AnsiName[NAME_SIZE];
        NameEntry(const NameEntry&) = delete;
        NameEntry(NameEntry&&) = delete;
        NameEntry& operator=(const NameEntry&) = delete;
        NameEntry& operator=(NameEntry&&) = delete;
    public:
        /** Returns whether this name entry is represented via WIDECHAR or ANSICHAR. */
        FORCEINLINE bool IsWide() const								{ return Header.bIsWide; }
        FORCEINLINE int32 GetNameLength() const 					{ return Header.Len; }
        FORCEINLINE bool IsNumbered() const 						{ return false; }
        /** Copy null-terminated name to ANSICHAR buffer without allocating. Entry must not be wide. */
        void GetAnsiName(ANSICHAR(&Out)[NAME_SIZE]) const;
        int32 GetSizeInBytes() const;

        static int32 GetDataOffset();
        NameStringView MakeView(NameBuffer& OptionalDecodeBuffer) const;
    private:
        friend class FName;
        friend struct FNameHelper;
        friend class FNameEntryAllocator;
        friend class FNamePoolShardBase;
        friend class FNamePool;
        void StoreName(const char* InName, uint32 Len);
        void CopyUnterminatedName(char* OutName) const;
        const char* GetUnterminatedName(char(&OptionalDecodeBuffer)[NAME_SIZE]) const;
    };
    /**
     * Legacy typedef - this is no longer an index
     *
     * Use GetTypeHash(FName) or GetTypeHash(NameEntryId) for hashing
     * To compare with ENames use FName(EName) or FName::ToEName() instead
     */
    typedef NameEntryId NAME_INDEX;
    /**
     * Public name, available to the world.  Names are stored as a combination of
     * an index into a table of unique strings and an instance number.
     * Names are case-insensitive, but case-preserving (when WITH_CASE_PRESERVING_NAME is 1)
     */
    class Name
    {
    public:
        FORCEINLINE NameEntryId GetComparisonIndex() const
        {
            return ComparisonIndex;
        }

        FORCEINLINE NameEntryId GetDisplayIndex() const
        {
            const NameEntryId Index = GetDisplayIndexFast();
            return Index;
        }
        FORCEINLINE int32 GetNumber() const
        {
            return Number;
        }

        FORCEINLINE void SetNumber(const int32 NewNumber)
        {
            Number = NewNumber;
        }
        
        /** Copy ANSI name without number part. Must *only* be used for ANSI FNames. Doesn't allocate. */
        void GetPlainANSIString(ANSICHAR(&AnsiName)[NAME_SIZE]) const;
        const NameEntry* GetComparisonNameEntry() const;
        const NameEntry* GetDisplayNameEntry() const;
        /**
         * Get the number of characters, excluding null-terminator, that ToString() would yield
         */
        uint32 GetStringLength() const;

        /**
         * Buffer size required for any null-terminated FName string, i.e. [name] '_' [digits] '\0'
         */
        static constexpr inline uint32 StringBufferSize = NAME_SIZE + 1 + 10; // NAME_SIZE includes null-terminator

        /**
         * Convert to string buffer to avoid dynamic allocations and returns string length
         *
         * Fails hard if OutLen < GetStringLength() + 1. StringBufferSize guarantees success.
         *
         * Note that a default constructed FName returns "None" instead of ""
         */
        uint32 ToString(TCHAR* Out, uint32 OutSize) const;

        template<int N>
        uint32 ToString(TCHAR (&Out)[N]) const
        {
            return ToString(Out, N);
        }
        /**
         * Check to see if this FName matches the other FName, potentially also checking for any case variations
         */
        FORCEINLINE bool IsEqual(const Name& Other, const ENameCase CompareMethod = ENameCase::IgnoreCase, const bool bCompareNumber = true) const;

        FORCEINLINE bool operator==(Name Other) const
        {
            return ToUnstableInt() == Other.ToUnstableInt();
        }

        FORCEINLINE bool operator!=(Name Other) const
        {
            return !(*this == Other);
        }
        /** Fast non-alphabetical order that is only stable during this process' lifetime. */
        FORCEINLINE bool FastLess(const Name& Other) const
        {
            return CompareIndexes(Other) < 0;
        }

        /** Slow alphabetical order that is stable / deterministic over process runs. */
        FORCEINLINE bool LexicalLess(const Name& Other) const
        {
            return Compare(Other) < 0;
        }

        /** True for FName(), FName(NAME_None) and FName("None") */
        FORCEINLINE bool IsNone() const
        {
    #if PLATFORM_64BITS && !WITH_CASE_PRESERVING_NAME
            return ToUnstableInt() == 0;
    #else
            return ComparisonIndex.IsNone() && GetNumber() == NAME_NO_NUMBER_INTERNAL;
    #endif
        }

        /**
         * Paranoid sanity check
         *
         * All FNames are valid except for stomped memory, dangling pointers, etc.
         * Should only be used to investigate such bugs and not in production code.
         */
        bool IsValid() const { return IsWithinBounds(ComparisonIndex); }

        /** Paranoid sanity check, same as IsValid() */
        bool IsValidIndexFast() const { return IsValid(); }
        bool IsValidXName() const;
        /**
         * Compares name to passed in one. Sort is alphabetical ascending.
         *
         * @param	Other	Name to compare this against
         * @return	< 0 is this < Other, 0 if this == Other, > 0 if this > Other
         */
        int32 Compare( const Name& Other ) const;

        /**
         * Fast non-alphabetical order that is only stable during this process' lifetime.
         *
         * @param	Other	Name to compare this against
         * @return	< 0 is this < Other, 0 if this == Other, > 0 if this > Other
         */
        FORCEINLINE int32 CompareIndexes(const Name& Other) const
        {
            if (int32 ComparisonDiff = ComparisonIndex.CompareFast(Other.ComparisonIndex))
            {
                return ComparisonDiff;
            }
            return GetNumber() - Other.GetNumber();
        }
        /**
         * Create an FName from an existing string, but with a different instance.
         *
         * @param Other The FName to take the string values from
         * @param InNumber The hardcoded value for the number portion of the name
         */
        FORCEINLINE Name(Name Other, int32 InNumber)
            : Name(CreateNumberedNameIfNecessary(Other.GetComparisonIndex(), Other.GetDisplayIndex(), InNumber))
        {
        }

        /**
         * Create an FName from its component parts
         * Only call this if you *really* know what you're doing
         */
        FORCEINLINE Name(NameEntryId InComparisonIndex, NameEntryId InDisplayIndex, int32 InNumber)
            : Name(CreateNumberedNameIfNecessary(InComparisonIndex, InDisplayIndex, InNumber))
        {
        }

    #if WITH_CASE_PRESERVING_NAME
        static NameEntryId GetComparisonIdFromDisplayId(NameEntryId DisplayId);
    #else
        static NameEntryId GetComparisonIdFromDisplayId(NameEntryId DisplayId) { return DisplayId; }
    #endif

        /**
         * Only call this if you *really* know what you're doing
         */
        static Name CreateFromDisplayId(NameEntryId DisplayId, int32 Number)
        {
            return Name(GetComparisonIdFromDisplayId(DisplayId), DisplayId, Number);
        }

        /**
         * Default constructor, initialized to None
         */
        FORCEINLINE Name(): Number(NAME_NO_NUMBER_INTERNAL)
        {
        }

        /**
         * Create an FName. If FindType is FNAME_Find, and the name 
         * doesn't already exist, then the name will be NAME_None.
         * The check for existance or not depends on UE_FNAME_OUTLINE_NUMBER.
         * When UE_FNAME_OUTLINE_NUMBER is 0, we only check for the string part.
         * When UE_FNAME_OUTLINE_NUMBER is 1, we check for whole name including the number.
         *
         * @param Name			Value for the string portion of the name
         * @param FindType		Action to take (see EFindName)
         */
        Name(const ANSICHAR* Name, EFindName FindType=FNAME_Add);

        /** Create FName from non-null string with known length  */
        Name(int32 Len, const ANSICHAR* Name, EFindName FindType=FNAME_Add);
        /**
         * Create an FName. Will add the string to the name table if it does not exist.
         * When UE_FNAME_OUTLINE_NUMBER is set, will also add the combination of base string and number to the name table if it doesn't exist.
         *
         * @param Name Value for the string portion of the name
         * @param Number Value for the number portion of the name
         */
        Name(const ANSICHAR* Name, int32 Number);
        Name(int32 Len, const ANSICHAR* Name, int32 Number);

        /**
         * Create an FName. If FindType is FNAME_Find, and the string part of the name 
         * doesn't already exist, then the name will be NAME_None
         *
         * @param Name Value for the string portion of the name
         * @param Number Value for the number portion of the name
         * @param FindType Action to take (see EFindName)
         * @param bSplitName true if the trailing number should be split from the name when Number == NAME_NO_NUMBER_INTERNAL, or false to always use the name as-is
         */
        static void Reserve(uint32 NumBytes, uint32 NumNames);

        /**
         * @return Size of all name entries.
         */
        static int32 GetNameEntryMemorySize();

        /**
         * @return Estimated remaining size the name entry table is willing to allocate.
         */
        static int32 GetNameEntryMemoryEstimatedAvailable();

        /**
        * @return Size of Name Table object as a whole
        */
        static int32 GetNameTableMemorySize();

        /**
         * @return number of ansi names in name table
         */
        static int32 GetNumAnsiNames();

        /**
         * @return number of wide names in name table
         */
        static int32 GetNumWideNames();

        static NameEntry const* GetEntry(NameEntryId Id);
        /** 
            Tear down system and free all allocated memory 
        
            FName must not be used after teardown
        */
        static void TearDown();

        /** Returns an integer that compares equal in the same way FNames do, only usable within the current process */
        FORCEINLINE uint64 ToUnstableInt() const
        {
            uint64 Out = 0;
            memcpy(&Out, this, sizeof(uint64));
            return Out;
        }

    public:
        /** Index into the Names array (used to find String portion of the string/number pair used for comparison) */
        NameEntryId	ComparisonIndex;
        uint32			Number;
    #if WITH_CASE_PRESERVING_NAME
        /** Index into the Names array (used to find String portion of the string/number pair used for display) */
        NameEntryId	DisplayIndex;
    #endif // WITH_CASE_PRESERVING_NAME
        FORCEINLINE NameEntryId GetDisplayIndexFast() const
        {
    #if WITH_CASE_PRESERVING_NAME
            return DisplayIndex;
    #else
            return ComparisonIndex;
    #endif
        }

        // Accessor for unmodified comparison index when UE_FNAME_OUTLINE_NUMBER is set
        FORCEINLINE NameEntryId GetComparisonIndexInternal() const
        {
            return ComparisonIndex;
        }

        // Accessor for unmodified display index when UE_FNAME_OUTLINE_NUMBER is set
        FORCEINLINE NameEntryId GetDisplayIndexInternal() const
        {
    #if WITH_CASE_PRESERVING_NAME
            return DisplayIndex;
    #else // WITH_CASE_PRESERVING_NAME
            return ComparisonIndex;
    #endif // WITH_CASE_PRESERVING_NAME
        }

        // Resolve the entry directly referred to by LookupId
        static const NameEntry* ResolveEntry(NameEntryId LookupId);
        // Recursively resolve through the entry referred to by LookupId to reach the allocated string entry, in the case of UE_FNAME_OUTLINE_NUMBER=1
        static const NameEntry* ResolveEntryRecursive(NameEntryId LookupId);

        static bool IsWithinBounds(NameEntryId Id);

        FORCEINLINE static Name CreateNumberedNameIfNecessary(NameEntryId ComparisonId, NameEntryId DisplayId, int32 Number)
        {
            Name Out;
            Out.ComparisonIndex = ComparisonId;
            Out.DisplayIndex = DisplayId;
            Out.Number = Number;
            return Out;
        }

        FORCEINLINE static Name CreateNumberedNameIfNecessary(NameEntryId ComparisonId, int32 Number)
        {
            return CreateNumberedNameIfNecessary(ComparisonId, ComparisonId, Number);
        }
        
        
        static bool Equals(Name A, Name B) = delete;
        static bool Equals(Name A, const ANSICHAR* B);
        friend FORCEINLINE uint32 GetTypeHash(Name Name)
        {
            return GetTypeHash(Name.GetComparisonIndex()) + Name.GetNumber();
        }
        template <typename T>
        friend FORCEINLINE auto operator==(Name N, T O) -> decltype(Name::Equals(N, O))
        {
            return Name::Equals(N, O);
        }

        template <typename T>
        friend FORCEINLINE auto operator==(T O, Name N) -> decltype(Name::Equals(N, O))
        {
            return Name::Equals(N, O);
        }

    #if !PLATFORM_COMPILER_HAS_GENERATED_COMPARISON_OPERATORS
        template <typename T>
        friend FORCEINLINE auto operator!=(Name N, T O) -> decltype(Name::Equals(N, O))
        {
            return !Name::Equals(N, O);
        }

        template <typename T>
        friend FORCEINLINE auto operator!=(T O, Name N) -> decltype(Name::Equals(N, O))
        {
            return !Name::Equals(N, O);
        }
    #endif
    };
    
/**
 * Serialization util that optimizes WITH_CASE_PRESERVING_NAME-loading by reducing comparison id lookups
 *
 * Stores 32-bit display entry id with an unused bit to indicate if FName::GetComparisonIdFromDisplayId lookup is needed.
 *
 * Note that only display entries should be saved to make output deterministic.
 */
class FDisplayNameEntryId
{
public:
	FDisplayNameEntryId() : FDisplayNameEntryId(Name()) {}
	explicit FDisplayNameEntryId(Name Name) : FDisplayNameEntryId(Name.GetDisplayIndex(), Name.GetComparisonIndex()) {}
	FORCEINLINE Name ToName(uint32 Number) const { return Name(GetComparisonId(), GetDisplayId(), Number); }

private:
	static constexpr uint32 DifferentIdsFlag = 1u << 31;
	static constexpr uint32 DisplayIdMask = ~DifferentIdsFlag;

	uint32 Value = 0;

	FDisplayNameEntryId(NameEntryId Id, NameEntryId CmpId) : Value(Id.ToUnstableInt() | (Id != CmpId) * DifferentIdsFlag) {}
	FORCEINLINE bool SameIds() const { return (Value & DifferentIdsFlag) == 0; }
	FORCEINLINE NameEntryId GetDisplayId() const { return NameEntryId::FromUnstableInt(Value & DisplayIdMask); }
	FORCEINLINE NameEntryId GetComparisonId() const { return SameIds() ? GetDisplayId() : Name::GetComparisonIdFromDisplayId(GetDisplayId()); }
	friend bool operator==(FDisplayNameEntryId A, FDisplayNameEntryId B) { return A.Value == B.Value; }
	friend bool operator==(NameEntryId A, FDisplayNameEntryId B) { return A == B.GetDisplayId(); }
	friend bool operator==(FDisplayNameEntryId A, NameEntryId B) { return A.GetDisplayId() == B; }
	[[nodiscard]] friend uint32 GetTypeHash(FDisplayNameEntryId InId) { return GetTypeHash(InId.GetDisplayId()); }

public: // Internal functions for batch serialization code - intentionally lacking CORE_API
	static FDisplayNameEntryId FromComparisonId(NameEntryId ComparisonId);
	NameEntryId ToDisplayId() const;
	void SetLoadedComparisonId(NameEntryId ComparisonId); // Called first
#if WITH_CASE_PRESERVING_NAME
	void SetLoadedDifferentDisplayId(NameEntryId DisplayId); // Called second if display id differs
	NameEntryId GetLoadedComparisonId() const; // Get the already loaded comparison id
#endif
};
}